Transformers¶

Predicción de emojis y visualización¶

Los transformers son una arquitectura medianamente reciente que ha tenido espectaculares resultados en procesamiento de lenguaje natural. Su estructura está basada en capas de atención, que a su vez tienen varias "cabezas". A diferencia de como se usa en redes neuronales recurrentes, las atenciones en un transformer son la base principal de la representación final del input, además de poder ser entrenadas en paralelo, y por ende en mayores volúmenes de datos de modo más eficiente.

arquitectura

(fuente imagen)

Para este proyecto hemos querido comparar resultados de estas arquitecturas con un clasificador visto en el curso que también es apropiado para la tarea de predicción, como lo es Naive-Bayes. Más aún, se usaran vectores que corresponden a tweets extraidos de estos modelos, y se analizarán usando herramientas de clustering revisadas en el curso. Para comprender las nociones del funcionamiento de la arquitectura y como modela los elementos linguisticos, observemos de forma resumida como funciona un módulo de atención:

arquitectura

Una manera de visualizar la arquitectura es pensar en una matriz, donde cada elemento codifica la importancia de un elemento de la sequencia de input con respecto a la secuencia de output. En este ejemplo mostramos una representación idealizada de un módulo de auto-atención (sefl attention), que es el modo en que los codificadores basados en transformers procesan las sequencias. Un poco más formalmente, consideramos matrices $W$ que serán aprendidas durante el entrenamientom y se relacionan del modo siguiente:

arquitectura

Visualizando Atenciones¶

Si bien la capacidad de interpretación que tienen los modelos va bajando a medida que la magnitud de estos sube, es interesante tener acceso al interior de los modelos. Con la biblioteca bertviz podemos visualizar las capas de atención para cada cabeza y cada capa para un input dado.

In [ ]:
from transformers import AutoTokenizer, AutoModelForSequenceClassification, utils
from bertviz import model_view, head_view
import csv
utils.logging.set_verbosity_error()  # Suppress standard warnings
from config import rank_emojis_text, labels_es

Cargando modelo

In [ ]:
MODEL = f"ccarvajal/beto-emoji"
folder = MODEL.replace('ccarvajal','modelos')

try:
    tokenizer = AutoTokenizer.from_pretrained(folder)
    model = AutoModelForSequenceClassification.from_pretrained(folder, output_attentions=True)
except ValueError:
    tokenizer = AutoTokenizer.from_pretrained(MODEL)
    tokenizer.save_pretrained(folder)
    model = AutoModelForSequenceClassification.from_pretrained(MODEL, output_attentions=True)
    model.save_pretrained(folder)
In [ ]:
ejemplo = "Tapas + sangria = @ Ocaña Barcelona"
rank_emojis_text(ejemplo,model,tokenizer,labels_es)
1) 😍 0.418
2) 🇪🇸 0.2464
3) 👌 0.097
4) 😊 0.0895
5) ❤ 0.0742
6) 😁 0.0254
7) 💙 0.0105
8) 😎 0.0093
9) 💜 0.0064
10) 💕 0.0048
11) 😉 0.004
12) 😜 0.0032
13) ✨ 0.0026
14) 💞 0.0025
15) 💘 0.0018
16) 😘 0.0016
17) 😂 0.0013
18) 💪 0.0008
19) 🎶 0.0007

Este ejemplo es uno de muchos que no está bien clasificado. El emoji en cuestión usado en el tweet es 🇪🇸 y no 😍.

Podemos usar este ejemplo para visualizar la capa de atención.

In [ ]:
def display_model_view(input_text):
    inputs = tokenizer.encode(input_text, return_tensors='pt')  # Tokenize input text
    outputs = model(inputs)  # Run model
    attention = outputs[-1]  # Retrieve attention from model outputs
    tokens = tokenizer.convert_ids_to_tokens(inputs[0])  # Convert input ids to token strings
    model_view(attention, tokens, include_layers=[11])
In [ ]:
display_model_view(ejemplo)
In [ ]:
def display_head_view(input_text):
    inputs = tokenizer.encode(input_text, return_tensors='pt')  # Tokenize input text
    outputs = model(inputs)  # Run model
    attention = outputs[-1]  # Retrieve attention from model outputs
    tokens = tokenizer.convert_ids_to_tokens(inputs[0])  # Convert input ids to token strings
    head_view(attention, tokens, layer=11)
In [ ]:
display_head_view(ejemplo)
Layer:

Un token que obviamos en el la representación idealizada es el token [CLS] (o < s > si la arquitectura de base es Roberta en vez de Bert). La representación de este token a través de la última capa es aquel que se usa como input para el clasificador, que es una capa feedforward con una función de activación. Al pasar el cursor por el token [CLS] a la izquierda podemos observar aquellos tokens que tienen más peso en el cómputo de aquella representación que se usará en la predicción.

En este caso, se observa una influencia más fuerte del token tapa, que es una comida típica de bar en Barcelona. Una hopótesis razonable es pensar que aquel token está "empujando" la clasificación hacia un emoji equivocado. Veamos lo que sucede cuando sólamente predecimos el emoji de la frase "tapas".

In [ ]:
rank_emojis_text("tapas",model,tokenizer,labels_es)
1) 😍 0.6418
2) ❤ 0.1665
3) 😊 0.0505
4) 👌 0.0341
5) 🇪🇸 0.0297
6) 💙 0.0228
7) 💜 0.0148
8) 💕 0.0143
9) 💘 0.0063
10) 💞 0.0045
11) 😁 0.0043
12) 😎 0.0039
13) ✨ 0.0028
14) 😘 0.0008
15) 😉 0.0008
16) 😜 0.0007
17) 😂 0.0006
18) 🎶 0.0004
19) 💪 0.0004

Se observa que el emoji 😍 es aquel con más probabilidad con un 64% de probabilidad, contra apenas 2,9% del token 🇪🇸.

Veamos otro ejemplo.

In [ ]:
otro_ejemplo = "Vamoooos!! #Gym #healthy @ Albacete Capital"
rank_emojis_text(otro_ejemplo,model,tokenizer,labels_es)
1) 💪 0.9921
2) 😁 0.0012
3) 😂 0.0011
4) 🇪🇸 0.0009
5) 👌 0.0007
6) 😜 0.0006
7) 😘 0.0006
8) 😉 0.0006
9) 😎 0.0004
10) 😊 0.0003
11) 💙 0.0003
12) 🎶 0.0003
13) 😍 0.0002
14) ❤ 0.0002
15) 💜 0.0001
16) ✨ 0.0001
17) 💘 0.0001
18) 💕 0.0001
19) 💞 0.0001

Acá la clasificación es correcta pues predice 💪 con alta probabilidad, que es efectivamente el label asociado. Observemos las capas de atención.

In [ ]:
display_model_view(otro_ejemplo)
In [ ]:
display_head_view(otro_ejemplo)
Layer:

Aquellos tokens con más influencia con respecto a [CLS] son gym, healthy y @. As interesante señalar que estas palabras son prestadas del inglés, y por ende sus tokens aparecen "cortados", puesto que el tokenizer está diseñado para español. Si hacemos una "españolización" de este mismo tweet obtenemos lo siguiente.

In [ ]:
otro_ejemplo = "Vamos!! #Gimnasio #saludable @ Albacete Capital"
rank_emojis_text(otro_ejemplo,model,tokenizer,labels_es)
1) 💪 0.9857
2) 😁 0.0023
3) 🇪🇸 0.0023
4) 😊 0.002
5) 😘 0.0015
6) 😉 0.0011
7) 👌 0.001
8) 😜 0.0009
9) 😎 0.0005
10) 💙 0.0005
11) 😂 0.0004
12) 🎶 0.0004
13) ✨ 0.0003
14) ❤ 0.0003
15) 💜 0.0002
16) 😍 0.0002
17) 💘 0.0002
18) 💕 0.0002
19) 💞 0.0002
In [ ]:
display_head_view(otro_ejemplo)
Layer:

La predicción sigue siendo la misma, además de que haya más influencia del token vamos que de @ en esta visualización.

Lo interesante de esto es observar como la arquitectura toma tokens y modela la relación entre ellos de modo que la comprensión que tiene de la frase sea consistente con su significado. Tener tokens cortados que provenian de otro idioma y tener vamos escrito de una manera más coloquial no fue impedimento para que la clasificación diera con el emoji correcto por un amplio margen.